package pl.edu.agh.ros.node;

import java.util.UUID;

import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.ros.internal.node.service.ServiceException;
import org.ros.internal.node.service.ServiceResponseBuilder;
import org.ros.node.DefaultNodeFactory;
import org.ros.node.NodeConfiguration;
import org.ros.node.service.ServiceServer;

import pl.edu.agh.ros.node.conf.ProxyNodeConfiguration;
import pl.edu.agh.ros.node.conf.XMPPConfiguration;
import pl.edu.agh.ros.node.manager.IServerManager;
import pl.edu.agh.ros.node.manager.XMPPManager;
import pl.edu.agh.ros.node.prop.Props;
import pl.edu.agh.ros.node.service.ServiceResponseManager;
import pl.edu.agh.ros.node.util.RosServiceUtils;
import pl.edu.agh.ros.node.util.ServiceObjectLatch;

import com.google.common.base.Preconditions;

public class ProxyServiceServer<Q extends org.ros.message.Message, A extends org.ros.message.Message> extends
		AbstractProxyNode {

	private final ServiceResponseManager<A> serviceResponseManager = new ServiceResponseManager<A>();
	private final RosServiceUtils<Q, A> rosServiceUtils;
	private String loggedUser;

	public ProxyServiceServer(IServerManager serverManager, XMPPConfiguration xmppConfiguration,
			ProxyNodeConfiguration proxyNodeConfiguration, String nodeName) {
		super(serverManager, xmppConfiguration, proxyNodeConfiguration, nodeName);
		this.rosServiceUtils = new RosServiceUtils<Q, A>();
	}

	public void main(NodeConfiguration configuration) {
		Preconditions.checkState(node == null);
		Preconditions.checkNotNull(configuration);

		try {

			xmppConnector = new XMPPManager();
			xmppConnector.connectAndLogin(xmppConfiguration);
			loggedUser = xmppConnector.getConnection().getUser().split("/")[0]; //$NON-NLS-1$
			createXMPPPacketListener();

			node = new DefaultNodeFactory().newNode(nodeName, configuration);
			ServiceServer<Q, A> server = node.newServiceServer(proxyNodeConfiguration.getName(), proxyNodeConfiguration
					.getType(), new ServiceResponseBuilder<Q, A>() {
				@Override
				public A build(Q request) throws ServiceException {
					UUID uuid = serviceResponseManager.generateObjectId();
					ServiceObjectLatch<A> futureObject = serviceResponseManager.createFutureObject(uuid);
					sendServiceRequest(request, uuid);
					try {
						A response = futureObject.get();
						if (response != null) {
							return response;
						} else {
							throw futureObject.getException();
						}
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block - proper error
						// handling
						e.printStackTrace();
						return null;
					}
				}
			});
			server.awaitRegistration();
		} catch (Exception e) {
			if (node != null) {
				node.getLog().fatal(e);
			} else {
				e.printStackTrace();
			}
		}
	}

	private void sendServiceRequest(Q request, UUID uuid) {
		Message xmppMessage = rosServiceUtils.serializeServiceRequest(request, node, proxyNodeConfiguration.getType());
		xmppMessage.setProperty(Props.XMPP_MSG_UUID, uuid);
		xmppMessage.setFrom(loggedUser);
		String subscriber = serverManager.getServiceProvider(proxyNodeConfiguration.getName());
		if (subscriber != null) {
			xmppConnector.sendMessage(xmppMessage, subscriber);
		} else {
			// TODO log message than no subscriber available
		}
	}

	private void onXMPPMessage(Message message) {
		if (message.getProperty(Props.XMPP_MSG_TYPE).equals(proxyNodeConfiguration.getType())) {
			UUID uuid = (UUID) message.getProperty(Props.XMPP_MSG_UUID);
			if (message.getProperty(Props.XMPP_MSG_VALUE) == null) {
				serviceResponseManager.onExceptionArrived(uuid, (ServiceException) message
						.getProperty(Props.XMPP_MSG_EXCEPTION));
			} else {
				A response = rosServiceUtils
						.deserializeServiceResponse(message, node, proxyNodeConfiguration.getType());
				serviceResponseManager.onResponseArrived(uuid, response);
			}
		}
	}

	private void createXMPPPacketListener() {
		PacketListener packetListener = new PacketListener() {
			public void processPacket(Packet packet) {
				if (packet instanceof Message) {
					Message message = (Message) packet;
					onXMPPMessage(message);
				}
			}
		};
		xmppConnector.getConnection().addPacketListener(packetListener, null);
	}

}